/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年8月19日
 */
package com.massyframework.beanskin.maven.plugin.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URL;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Objects;
import java.util.Properties;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;

import com.massyframework.beanskin.maven.plugin.Module;
import com.massyframework.beanskin.maven.plugin.ras.BCRSAKeyReader;
import com.massyframework.beanskin.maven.plugin.ras.BCRSAService;
import com.massyframework.beanskin.maven.plugin.ras.RSAKeyReader;
import com.massyframework.beanskin.maven.plugin.ras.RSAService;

/**
 * @author huangkh
 *
 */
public abstract class BouncycastleUtils {
	
	private static volatile BouncyCastleProvider PROVIDER ;
	//算法名
    private static final String KEY_ALGORITHM = "AES";
    //加解密算法/模式/填充方式
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
    
    public static void init() throws Exception{
    	if (PROVIDER == null) {
			PROVIDER = new BouncyCastleProvider();
			Security.addProvider(PROVIDER);		
		}
    }
    
    /**
     * 创建RSAService 
     * @return {@link RSAService}
     * @throws Exception
     */
    public static RSAService createRasService() throws Exception {
    	init();
    	
    	return new BCRSAService();
    }
    
    /**
     * 创建RSAKeyReader
     * @return {@link RSAKeyReader}
     * @throws Exception
     */
    public static RSAKeyReader createRSAKeyReader() throws Exception {
    	init();
    	
    	return new BCRSAKeyReader();
    }
    
	/**
	 * 加载配置文件
	 */
	public static Properties loadProperties() throws IOException{
		Properties result = new Properties();
		
		URL url = BouncycastleUtils.class.getClassLoader().getResource("keys.properties");
		InputStream is = null;
		try {
			is = url.openStream();
			
			result.load(is);
			return result;
		}finally {
			if (is != null) {
				try {
					is.close();
				}catch(Exception e) {
					
				}
			}
		}
	}
	
	/**
	 * 加载公钥
	 * @param props {@link Properties},配置文件
	 * @return {@link PublicKey}
	 * @throws Exception
	 */
	public static PublicKey loadPublicKey(Properties props) throws Exception{
		return loadPublicKey(props.getProperty("publicKey"));
	}
	
	/**
	 * 加载共享的公钥
	 * @param props {@link Properties}
	 * @return {@link PublicKey}
	 * @throws Exception
	 */
	public static PublicKey loadSharedKey(Properties props) throws Exception{
		return loadPublicKey(props.getProperty("sharedKey"));
	}
	
	/**
	 * 加载公钥
	 * @param pubkey {@link String},公钥字符串
	 * @return {@link PublicKey}
	 * @throws Exception
	 */
	public static PublicKey loadPublicKey(String pubkey) throws Exception{
		init();
		
		ECParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
		ECPublicKeySpec pubKey = new ECPublicKeySpec(
					params.getCurve().decodePoint(Hex.decode(pubkey)), params);
		KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
		return kf.generatePublic(pubKey);
	}
	
	/**
	 * 加载私钥
	 * @param props
	 * @return
	 * @throws Exception
	 */
	public static PrivateKey loadPrivateKey(Properties props) throws Exception{
		return loadPrivateKey(props.getProperty("privateKey"));
	}
	
	/**
	 * 加载私钥
	 * @param privkey
	 * @return
	 * @throws Exception
	 */
	public static PrivateKey loadPrivateKey(String privkey) throws Exception{
		init();
		
		ECParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
		ECPrivateKeySpec prvkey = new ECPrivateKeySpec(
				new BigInteger(Hex.decode(privkey)), params);
		KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
		return kf.generatePrivate(prvkey);
	}
	
	/**
	 * 生成密钥对
	 * @return {@link KeyPair}
	 * @throws Exception 
	 */
	public static KeyPair genericKeyPair() throws Exception {
		KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "BC");
		kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom());
		
		return kpgen.generateKeyPair();
	}
	
	/**
	 * 计算加密密钥
	 * @param module {@link Module},模块
	 * @param privKey {@link PrivateKey},私钥
	 * @param pubKey {@link PublicKey}, 公钥
	 * @return {@link byte}数组
	 */
	public static byte[] calcutaionKey(Module module, PrivateKey privKey, PublicKey pubKey)  throws Exception{				
		KeyAgreement ka = KeyAgreement.getInstance("ECDH", "BC");
		ka.init(privKey);
		ka.doPhase(pubKey, true);
		
		//计算出密钥
		byte[] secret = ka.generateSecret();
		String context = module.getGroupId().concat(":").concat(module.getModuleId()).concat(":").concat(module.getVersion());
		String digestString = DigestUtils.md5Hex(context);
		
		
		char[] digest = digestString.toCharArray();
		int length = digest.length;
		for (int i=0; i<length ; i++) {
			byte b = (byte)digest[i];
			secret[i]= (byte) (secret[i]^b);
		}
				
		return Arrays.copyOfRange(secret, 9, 25);
	}
	
	/**
	 * 创建Aes加密密码
	 * @param key {@link byte[]}，密钥
	 * @return {@link Cipher}
	 * @throws InvalidKeyException
	 * @throws InvalidAlgorithmParameterException
	 * @throws Exception
	 */
	public static Cipher createEncryptCipher(byte[] key) throws InvalidKeyException, InvalidAlgorithmParameterException, Exception {
		Objects.requireNonNull(key, "\"key\" cannot be null.");
		if (key.length != 16){
			 throw new IllegalArgumentException("key length is not 16 byte.");
	    }

		SecretKey secretKey = new SecretKeySpec(key,KEY_ALGORITHM);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        //设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, generateIV());
        return cipher;
	}
	
	/**
	 * 创建Aes解密密码
	 * @param key {@link byte[]}，密钥
	 * @return {@link Cipher}
	 * @throws InvalidKeyException
	 * @throws InvalidAlgorithmParameterException
	 * @throws Exception
	 */
	public static Cipher createDecryptCipher(byte[] key)throws InvalidKeyException, InvalidAlgorithmParameterException, Exception {
		Objects.requireNonNull(key, "\"key\" cannot be null.");
		if (key.length != 16){
			 throw new IllegalArgumentException("key length is not 16 byte.");
	    }

		SecretKey secretKey = new SecretKeySpec(key,KEY_ALGORITHM);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        //设置为加密模式
        cipher.init(Cipher.DECRYPT_MODE, secretKey, generateIV());
        return cipher;
	}
	
	 /**
     * 生成iv
     * @return {@link AlgorithmParameters}
     * @throws Exception
     */
    private static AlgorithmParameters generateIV() throws Exception{
        //iv 为一个 16 字节的数组，这里采用和 iOS 端一样的构造方法，数据全为0
        byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_ALGORITHM);
        params.init(new IvParameterSpec(iv));
        return params;
    }
	
	/**
	 * 对{@code inputStream}的类文件进行加密
	 * @param cipher {@link Cipher}
	 * @param inputStream {@link InputStream},输入流
	 * @throws IOException 
	 * @throws BadPaddingException 
	 * @throws IllegalBlockSizeException 
	 * @throws ClassNotFoundException 
	 */
	public static byte[] encryptClass(Cipher cipher, InputStream inputStream) 
			throws IOException, IllegalBlockSizeException, BadPaddingException, ClassNotFoundException {
		Objects.requireNonNull(cipher, "\"cipher\" cannot be null.");
		Objects.requireNonNull(inputStream, "\"inputStream\" cannot be null.");
			
		byte[] header = new byte[8];
		byte[] data = new byte[inputStream.available()-8];
		inputStream.read(header);
		inputStream.read(data);
		data = cipher.doFinal(data);
		byte[] result = new byte[data.length + 8];
		System.arraycopy(header, 0, result, 0, 8);
		System.arraycopy(data, 0, result, 8, data.length);
		return result;
	}
	
	/**
	 * 对{@code inputStream}的文件进行加密
	 * @param cipher {@link Cipher}
	 * @param inputStream {@link InputStream},输入流
	 * @throws IOException 
	 * @throws BadPaddingException 
	 * @throws IllegalBlockSizeException 
	 * @throws ClassNotFoundException 
	 */
	public static byte[] encryptFile(Cipher cipher, InputStream inputStream) 
			throws IOException, IllegalBlockSizeException, BadPaddingException, ClassNotFoundException {
		Objects.requireNonNull(cipher, "\"cipher\" cannot be null.");
		Objects.requireNonNull(inputStream, "\"inputStream\" cannot be null.");
			
		byte[] data = new byte[inputStream.available()];
		inputStream.read(data);
		return cipher.doFinal(data);
	}
	
	/**
	 * 对{@code data}数据进行解密
	 * @param cipher {@link Cipher}
	 * @param data 加密数组
	 * @return {@link inputStream}
	 * @throws IOException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws ClassNotFoundException
	 */
	public static byte[] decryptFile(Cipher cipher, InputStream inputStream) 
			throws IOException, IllegalBlockSizeException, BadPaddingException, ClassNotFoundException{
		Objects.requireNonNull(cipher, "\"cipher\" cannot be null.");
		Objects.requireNonNull(inputStream, "\"inputStream\" cannot be null.");
		
		byte[] data = new byte[inputStream.available()];
		inputStream.read(data);
		return cipher.doFinal(data);
	}
	
	
	/**
	 * 加密字符串
	 * @param cipher {@link Cipher}，密钥
	 * @param content {@link String},加密内容
	 * @return {@link byte},密文字节
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws UnsupportedEncodingException
	 */
	public static byte[] encrypt(Cipher cipher, String content) 
			throws  IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
		Objects.requireNonNull(cipher, "\"cipher\" cannot be null.");
		Objects.requireNonNull(content, "\"content\" cannot be null.");
		
		return cipher.doFinal(content.getBytes());
	}
	
	
	
	/**
	 * 
	 * @param key
	 * @return
	 */
	public static String hexToString(PublicKey key) {
		ECPublicKey eckey = (ECPublicKey)key;
		byte[] data = eckey.getQ().getEncoded(true);
		return Hex.toHexString(data);
	}
}
